use std::{
    collections::HashMap,
    sync::atomic::{AtomicUsize, Ordering},
};

use common_base::{once_cell::sync::Lazy, smallvec::SmallVec};

use super::{BizMetadata, DctMetadata, DoMetadata, FctMetadata, Metadata, MetadataType};

#[derive(Debug, Default)]
pub struct MetadataPool {
    biz_md_ref_stack: SmallVec<[BizMetadata; 1024]>,
    biz_md_ref_map: HashMap<String, usize>,
    biz_md_ref_id_generator: AtomicUsize,
    //
    dct_md_ref_stack: SmallVec<[DctMetadata; 1024]>,
    dct_md_ref_map: HashMap<String, usize>,
    dct_md_ref_id_generator: AtomicUsize,
    //
    fct_md_ref_stack: SmallVec<[FctMetadata; 1024]>,
    fct_md_ref_map: HashMap<String, usize>,
    fct_md_ref_id_generator: AtomicUsize,
    //
    do_md_ref_stack: SmallVec<[DoMetadata; 1024]>,
    do_md_ref_map: HashMap<String, usize>,
    do_md_ref_id_generator: AtomicUsize,
}

unsafe impl Sync for MetadataPool {}

static mut METADATA_POOL: Lazy<MetadataPool> = Lazy::new(|| MetadataPool::default());

impl MetadataPool {
    pub fn register(metadata: Metadata) -> usize {
        unsafe {
            if let Some(ref_id) = METADATA_POOL.find_ref_id(&metadata) {
                ref_id
            } else {
                METADATA_POOL.push_ref(metadata)
            }
        }
    }

    pub fn find_biz_metadata_by_ref_id(&self, ref_id: usize) -> Option<&BizMetadata> {
        if ref_id >= self.biz_md_ref_stack.len() {
            None
        } else {
            self.biz_md_ref_stack.get(ref_id)
        }
    }

    fn find_ref_id(&self, metadata: &Metadata) -> Option<usize> {
        let object_id = metadata.get_object_id();
        match metadata {
            Metadata::Biz(_) => self.biz_md_ref_map.get(object_id),
            Metadata::Dct(_) => self.dct_md_ref_map.get(object_id),
            Metadata::Fct(_) => self.fct_md_ref_map.get(object_id),
            Metadata::Do(_) => self.do_md_ref_map.get(object_id),
        }
        .and_then(|x| Some(*x))
    }

    fn push_ref(&mut self, metadata: Metadata) -> usize {
        match metadata {
            Metadata::Biz(inner) => {
                let ref_id = self.biz_md_ref_id_generator.fetch_add(1, Ordering::SeqCst);
                self.biz_md_ref_map
                    .insert(inner.base.object_id.to_owned(), ref_id);
                self.biz_md_ref_stack.insert(ref_id, inner);
                ref_id
            }
            Metadata::Dct(inner) => {
                let ref_id = self.biz_md_ref_stack.len();
                self.dct_md_ref_map
                    .insert(inner.base.object_id.to_owned(), ref_id);
                self.dct_md_ref_stack.insert(ref_id, inner);
                ref_id
            }
            Metadata::Fct(inner) => {
                let ref_id = self.biz_md_ref_stack.len();
                self.fct_md_ref_map
                    .insert(inner.base.object_id.to_owned(), ref_id);
                self.fct_md_ref_stack.insert(ref_id, inner);
                ref_id
            }
            Metadata::Do(inner) => {
                let ref_id = self.biz_md_ref_stack.len();
                self.do_md_ref_map
                    .insert(inner.base.object_id.to_owned(), ref_id);
                self.do_md_ref_stack.insert(ref_id, inner);
                ref_id
            }
        }
    }
}
